﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.ComponentModel;
using System.Reflection;

namespace WebPALTT.Lib
{
    /// <summary>
    /// An If Statement Action. Uses a script code statement to run one of two folders.
    /// </summary>
    [Serializable]
    public class ActionItem_If : ScriptItem
    {
        /// <summary>
        /// Static method containing the CustomType of this action for comparison.
        /// </summary>
        public static string Type { get { return "If Statement"; } }
        [CategoryAttribute("Action Properties"), ReadOnlyAttribute(true)]
        public override string CustomType { get { return ActionItem_If.Type; } }        
        [CategoryAttribute("Action Properties"), ReadOnlyAttribute(true)]
        public override System.Drawing.Image Icon { get { return global::WebPALTT.Lib.Icons.script_code; } }
        
        public override bool HasChildren { get { return true; } }
        public override bool CanAddChildren { get { return false; } }
        public override ActionItemCollection Children { get { return mActions; } }
        public override ExecutionStatus ExecutionStatus { get { return base.mExecutionStatus; } }
        public override string VisibleCode
        {
            get { return "if(" + Condition + ")\n{\n\tThen;\n}\nelse\n{\n\tElse;\n}"; }
            set { throw new InvalidOperationException("Unable to write to this code."); }
        }
        public override string ScriptCode
        {
            get { return "public bool @" + Name + "() {return " + (String.IsNullOrWhiteSpace(Condition) ? "false" : Condition) + ";}\r\n"; }
        }
        public override bool IsCodeEditable { get { return false; } }
        public override string[] DataHeaders { get { return new string[] { "Result" }; } }
        public override string[] DataHeaders_Save { get { return new string[] { "Result" }; } }

        string mCondition;
        ActionItemCollection mActions;
        ActionItem mUnusedAction;
        bool mExecResult;

        /// <summary>
        /// Gets or sets the condition to evaluate for this action.
        /// NB: Changing the condition during runtime (from a script action) will not affect the outcome. 
        /// </summary>
        [DescriptionAttribute("Condition to evaluate at runtime.\nNB: while the condition is evaluated at runtime it cannot be changed at runtime.")]
        [Editor(typeof(System.ComponentModel.Design.MultilineStringEditor), typeof(System.Drawing.Design.UITypeEditor))]
        public string Condition { get { return mCondition; } set { mCondition = value; } }
        /// <summary>
        /// Gets or sets the action item collection to run if the condition is true.
        /// </summary>
        [BrowsableAttribute(false)]
        public ActionItem_Folder ThenActions { get { return (ActionItem_Folder)mActions[0]; } }
        /// <summary>
        /// Gets or sets the action item collection to run if the condition is false.
        /// </summary>
        [BrowsableAttribute(false)]
        public ActionItem_Folder ElseActions { get { return (ActionItem_Folder)mActions[1]; } }

        public ActionItem_If() : this("") { }

        public ActionItem_If(string name) : this(name, "true", null, null) { }

        public ActionItem_If(string name, string condition, ActionItem_Folder thenactions, ActionItem_Folder elseactions)
            : base(name)
        {
            mCondition = condition;
            mActions = new ActionItemCollection();
            mActions.Add(thenactions == null ? new ActionItem_Folder("Then") : thenactions);
            mActions.Add(elseactions == null ? new ActionItem_Folder("Else") : elseactions);
            mActions.IsReadOnly = true;
        }

        public ActionItem_If(SerializationInfo info, StreamingContext ctxt)
            : base(info, ctxt)
        {
            mCondition = (string)info.GetValue("Condition", typeof(string));
            try
            {
                mActions = (ActionItemCollection)info.GetValue("Actions", typeof(ActionItemCollection));
            }
            catch (SerializationException) 
            {
                mActions = new ActionItemCollection();
                mActions.Add(new ActionItem_Folder("Then",(ActionItemCollection)info.GetValue("ThenActions", typeof(ActionItemCollection))));
                mActions.Add(new ActionItem_Folder("Else",(ActionItemCollection)info.GetValue("ElseActions", typeof(ActionItemCollection))));
                mActions.IsReadOnly = true;
            }
        }

        public override void GetObjectData(SerializationInfo info, StreamingContext ctxt)
        {
            base.GetObjectData(info, ctxt);
            info.AddValue("Condition", mCondition);
            info.AddValue("Actions", mActions);
        }

        public override string[] Validate()
        {
            List<string> errors = new List<string> { };
            if (Name.Equals("Then") || Name.Equals("Else") || Name.Equals("Actions"))
                errors.Add("Error: action[" + Name + "(" + ActionType + ")] is using a reserverd name.");
            if (Name.IndexOfAny(new char[] { ' ', '!', '@', '#', '$', '%', '^', '&', '*', '\'', '\"', 
                '(', ')', '[', ']', '{', '}', ';', ':', '.', ',', '\\', '/' }) > -1)
                errors.Add("Error: action[" + Name + "(" + ActionType + ")].Name contains invalid characters.");

            if (ThenActions.Children.Count == 0 && ElseActions.Children.Count == 0)
                errors.Add("Warning: action[" + Name + "(" + ActionType + ")]. - Conditional action doesn't contain any sub actions");
            errors.AddRange(ThenActions.Validate());
            errors.AddRange(ElseActions.Validate());

            return errors.ToArray();
        }

        public override void SetParent(ActionItem parent, int index = 0)
        {
            base.SetParent(parent, index);
            ThenActions.SetParent(this, 0);
            ElseActions.SetParent(this, 1);
        }

        public override ActionItem FindChild(Guid key)
        {
            return this.Children.Find(
                delegate(ActionItem item)
                {
                    if (Guid.Equals(item.Key, key))
                        return true;
                    return false;
                }, true);
        }

        public override ActionItem[] FindChildren(Guid key)
        {
            List<ActionItem> list = new List<ActionItem> { };
            list.AddRange(this.Children.FindAll(
                delegate(ActionItem item)
                {
                    if (Guid.Equals(item.Key, key))
                        return true;
                    return false;
                }, true));
            return list.ToArray();
        }

        public override Dictionary<string, object> Execute(Scenario scenario, ScenarioResults resultScene, Type scriptType, object scriptObj)
        {
            Dictionary<string, object> results = new Dictionary<string, object>();
            if (base.mExecutionStatus == Lib.ExecutionStatus.RunChildren)
            {
                base.mExecutionStatus = Lib.ExecutionStatus.Complete;
                this.mActions.IsReadOnly = false; 
                if (mExecResult)
                    this.mActions.Insert(1, mUnusedAction);
                else
                    this.mActions.Insert(0, mUnusedAction);
                this.mActions.IsReadOnly = true;
                return null;
            }
            else
            {                
                base.mExecutionStatus = Lib.ExecutionStatus.RunChildren;
                mExecResult = false;
                MethodInfo _methodInfo = scriptType.GetMethod(this.Name);
                mExecResult = (bool)_methodInfo.Invoke(scriptObj, null);
                this.mActions.IsReadOnly = false;
                if (mExecResult)
                {
                    this.mUnusedAction = mActions[1];
                    this.mActions.RemoveAt(1);
                }
                else
                {
                    this.mUnusedAction = mActions[0];
                    this.mActions.RemoveAt(0);
                }
                this.mActions.IsReadOnly = true;

                results.Add("Result", mExecResult);
                return results;
            }
        }
    }
}
